﻿using System;
using System.Collections.Generic;
using System.Text;

namespace Estructuras.Funciones.Analizar
{
    internal class ObtenerTags
    {
        /// <summary>
        /// Tìpos para definir si se ha encontrado un valor o una máscara
        /// </summary>
        enum TiposClaves
        {
            Valores,
            Mascaras
        };

        /// <summary>
        /// Claves de la tabla.
        /// </summary>
        private List<string> claves = null;

        /// <summary>
        /// Valores asociados a cada clave.
        /// </summary>
        private List<Valor> listaTags = null;

        /// <summary>
        /// Claves máscara del diccionario de entrada.
        /// </summary>
        private List<string> clavesMascaras = null;

        /// <summary>
        /// Valores asociados a cada clave máscara.
        /// </summary>
        private List<Mascara> listaMascaras = null;

        /// <summary>
        /// Lista de separadores.
        /// </summary>
        private List<char> separadores = null;

        /// <summary>
        /// Lista de caracteres especiales.
        /// </summary>
        private List<char> caracteresEspeciales = null;

        /// <summary>
        /// Lista de datos no asignados.
        /// </summary>
        private List<string> datosNoAsignados = null;

        /// <summary>
        /// Obtiene la lista de datos no asignados.
        /// </summary>
        public List<string> DatosNoAsignados
        {
            get
            {
                if (datosNoAsignados == null)
                    datosNoAsignados = new List<string>();
                return datosNoAsignados;
            }
        }

        /// <summary>
        /// Constructor.
        /// </summary>
        /// <param name="listaValores">Lista de claves del diccionario con sus correspondientes valores</param>
        /// <param name="separadores">Lista de separadores a emplear</param>
        /// <param name="caracteresEspeciales">Lista de caracteres especiales</param>
        public ObtenerTags(Dictionary<string, Valor> listaValores, Dictionary<string, Mascara> listaMascaras, List<char> separadores, List<char> caracteresEspeciales)
        {
            this.separadores = separadores;
            this.caracteresEspeciales = caracteresEspeciales;
            // Valores
            this.claves = new List<string>();
            this.listaTags = new List<Valor>();
            foreach (string valor in listaValores.Keys)
            {
                claves.Add(valor);
                listaTags.Add(listaValores[valor]);
            }
            // Mascaras
            this.clavesMascaras = new List<string>();
            this.listaMascaras = new List<Mascara>();
            foreach (string mascara in listaMascaras.Keys)
            {
                this.clavesMascaras.Add(mascara);
                this.listaMascaras.Add(listaMascaras[mascara]);
            }
        }

        /// <summary>
        /// Analiza el dato indicado.
        /// </summary>
        /// <param name="dato">Dato a analizar</param>
        /// <param name="arbol">Árbol sobre el que se construirá la estructura de decodificación del dato</param>        
        public void Analizar(string dato, ref ArbolBinario<ValorSalida> arbol)
        {
            char[] sep = new char[separadores.Count];
            this.separadores.CopyTo(sep);
            if (!string.Empty.Equals(dato.Trim(sep)))
            {
                // Comprueba si se encuentra en el dato los valores de las listas
                int posicion = -1;
                int indice = -1;
                TiposClaves tipo = TiposClaves.Valores;
                if (this.BuscarCoincidencia(dato, ref posicion, ref indice, ref tipo))
                {
                    // Si se encuentran, se busca el tag asociado y se realiza lo mismo con el resto del dato
                    string anterior = dato.Substring(0, posicion);
                    string posterior = string.Empty;
                    string datoEncontrado = string.Empty;
                    if (tipo == TiposClaves.Valores)
                    {
                        datoEncontrado = dato.Substring(posicion, claves[indice].Length);
                        posterior = dato.Substring(posicion + claves[indice].Length);
                    }
                    else
                    {
                        datoEncontrado = dato.Substring(posicion, clavesMascaras[indice].Length);
                        posterior = dato.Substring(posicion + clavesMascaras[indice].Length);
                    }

                    string tagActual = string.Empty;
                    string salidaActual = string.Empty;
                    Dictionary<string, string> otrosTags = null;
                    this.ObtenerDatosTagYSalida(indice, tipo, datoEncontrado, ref tagActual, ref salidaActual, ref otrosTags);

                    ArbolBinario<ValorSalida>.Insertar(ref arbol, new ValorSalida(datoEncontrado, tagActual, salidaActual, otrosTags));
                    this.Analizar(anterior, ref arbol.nodoIzq);
                    this.Analizar(posterior, ref arbol.nodoDer);
                }
                else
                {
                    // Si no, se establece un tag genérico por cada palabra del dato
                    string palabra = dato.Split(sep, StringSplitOptions.RemoveEmptyEntries)[0];
                    this.DatosNoAsignados.Add(palabra);
                    string tagPalabra = this.BuscarTagGenerico(palabra);
                    ArbolBinario<ValorSalida>.Insertar(ref arbol, new ValorSalida(palabra, tagPalabra, palabra, null));
                    if (!dato.Trim(sep).Equals(palabra))
                    {
                        string resto = dato.Trim(sep).Substring(palabra.Length);
                        this.Analizar(resto, ref arbol.nodoDer);
                    }
                }
            }
        }

        /// <summary>
        /// Obtiene los datos del tag y el valor de salida de la lista con el índice indicado.
        /// </summary>
        /// <param name="indice">Índice de la entrada del diccionario obtenida</param>
        /// <param name="tipo">Tipo de valor encontrado y sobre el que buscar el dato</param>
        /// <param name="valorEncontrado">Valor encontrado en el dato de entrada</param>
        /// <param name="tag">Tag asociado a la entrada</param>
        /// <param name="salida">Salida asociada a la entrada</param>
        /// <param name="otrosTags">Otros tags de salida asociados a la entrada</param>
        private void ObtenerDatosTagYSalida(int indice, TiposClaves tipo, string valorEncontrado, ref string tag, ref string salida, ref Dictionary<string, string> otrosTags)
        {
            // Valores
            if (tipo == TiposClaves.Valores)
            {
                // Si no es enlace obtiene el tag y la salida directamente
                if (!listaTags[indice].EsEnlace)
                {
                    tag = listaTags[indice].Tag;
                    if (string.Empty.Equals(listaTags[indice].SalidaModificada))
                        salida = claves[indice];
                    else
                        salida = listaTags[indice].SalidaModificada;
                    otrosTags = listaTags[indice].OtrosTags;
                }
                else
                {
                    // Si es enlace, avanza hasta encontrar el destino del enlace
                    Valor valor = listaTags[indice];
                    int grado = 0;
                    string clave = string.Empty;
                    while (valor != null && valor.EsEnlace && grado < 5)
                    {
                        int indiceBuscado = claves.IndexOf(valor.EnlaceA);
                        clave = valor.EnlaceA;
                        if (indiceBuscado != -1)
                        {
                            valor = listaTags[indiceBuscado];
                        }
                        else
                            valor = null;
                        grado++;
                    }
                    if (grado == 5 || valor == null)
                    {
                        tag = string.Empty;
                        salida = string.Empty;
                        otrosTags = null;
                    }
                    else
                    {
                        tag = valor.Tag;
                        if (string.Empty.Equals(valor.SalidaModificada))
                            salida = clave;
                        else
                            salida = valor.SalidaModificada;
                        otrosTags = listaTags[indice].OtrosTags;
                    }
                }
            }
            else
            {
                // Máscaras
                tag = listaMascaras[indice].Tag;
                salida = listaMascaras[indice].obtenerDatoSalida(valorEncontrado, clavesMascaras[indice]);
            }
        }

        /// <summary>
        /// Obtiene un tag genérico para el dato indicado.
        /// </summary>
        /// <param name="dato">Dato del que obtener el tag</param>
        /// <returns>Tag genérico para el dato indicado</returns>
        private string BuscarTagGenerico(string dato)
        {
            string representacion = string.Empty;
            foreach (char caracter in dato.ToCharArray())
            {
                if (char.IsLetter(caracter))
                    representacion += "A";
                else if (char.IsDigit(caracter))
                    representacion += "N";
                else if (this.caracteresEspeciales.Contains(caracter))
                    representacion += "S";
                else
                    representacion += "O";
            }
            if (representacion.Length > 1)
            {
                if (representacion.Contains("A") && representacion.Contains("N") && representacion.Contains("S") && representacion.Contains("O"))
                    return "ALFANUMERICO";
                else if (representacion.Contains("A") && representacion.Contains("N") && representacion.Contains("S") && !representacion.Contains("O"))
                    return "ALFANUMERICO";
                else if (representacion.Contains("A") && representacion.Contains("N") && representacion.Contains("O") && !representacion.Contains("S"))
                    return "ALFANUMERICO";
                else if (representacion.Contains("A") && representacion.Contains("S") && representacion.Contains("O") && !representacion.Contains("N"))
                    return "CODIGO";
                else if (representacion.Contains("N") && representacion.Contains("S") && representacion.Contains("O") && !representacion.Contains("A"))
                    return "CODIGO";
                else if (representacion.Contains("A") && representacion.Contains("N") && !representacion.Contains("S") && !representacion.Contains("O"))
                    return "ALFANUMERICO";
                else if (representacion.Contains("A") && representacion.Contains("S") && !representacion.Contains("N") && !representacion.Contains("O"))
                    return "CODIGO";
                else if (representacion.Contains("A") && representacion.Contains("O") && !representacion.Contains("N") && !representacion.Contains("S"))
                    return "CODIGO";
                else if (representacion.Contains("N") && representacion.Contains("S") && !representacion.Contains("O") && !representacion.Contains("A"))
                    return "CODIGO";
                else if (representacion.Contains("N") && representacion.Contains("O") && !representacion.Contains("S") && !representacion.Contains("A"))
                    return "CODIGO";
                else if (representacion.Contains("O") && representacion.Contains("S") && !representacion.Contains("A") && !representacion.Contains("A"))
                    return "OTRO";
                else if (representacion.Contains("A") && !representacion.Contains("N") && !representacion.Contains("S") && !representacion.Contains("O"))
                    return "PALABRA";
                else if (representacion.Contains("N") && !representacion.Contains("A") && !representacion.Contains("S") && !representacion.Contains("O"))
                    return "NUMERO";
                else if (representacion.Contains("S") && !representacion.Contains("N") && !representacion.Contains("A") && !representacion.Contains("O"))
                    return "SPECIAL";
                else if (representacion.Contains("O") && !representacion.Contains("S") && !representacion.Contains("N") && !representacion.Contains("A"))
                    return "OTRO";
                else
                    return string.Empty;
            }
            else
            {
                if ("A".Equals(representacion))
                    return "CARACTER";
                else if ("N".Equals(representacion))
                    return "NUMERO-1";
                else if ("S".Equals(representacion))
                    return "SPECIAL-1";
                else if ("O".Equals(representacion))
                    return "OTRO-1";
                else
                    return string.Empty;
            }
        }

        /// <summary>
        /// Busca una entrada en la tabla en el dato proporcionado.
        /// Si existe alguna devuelve la posición en la que se encuentra y el índice de la entrada de la tabla.
        /// </summary>
        /// <param name="dato">Dato en el que buscar las entradas del diccionario</param>
        /// <param name="posicion">Posición dentro del dato de la entrada encontrada</param>
        /// <param name="index">Índice de la entrada encontrada en el diccionario</param>
        /// <param name="tipo">Variable para determinar dónde se ha encontrado el dato</param>
        /// <returns>Encuentra coincidencia de algún dato del diccionario dentro del dato indicado</returns>
        private bool BuscarCoincidencia(string dato, ref int posicion, ref int index, ref TiposClaves tipo)
        {
            // Buscar valores
            bool encontrado = false;
            int indice = 0;
            int pos = -1;
            while (indice < claves.Count && !encontrado)
            {
                pos = dato.IndexOf(claves[indice]);
                if (pos != -1)
                    encontrado = true;
                indice++;
            }
            if (encontrado)
            {
                posicion = pos;
                index = indice - 1;
                tipo = TiposClaves.Valores;
            }
            else
            {
                // Buscar mascaras
                string mascaraEntrada = Mascara.obtenerMascara(dato);
                indice = 0;
                while (indice < clavesMascaras.Count && !encontrado)
                {
                    pos = mascaraEntrada.IndexOf(clavesMascaras[indice]);
                    if (pos != -1)
                        encontrado = true;
                    indice++;
                }
                if (encontrado)
                {
                    posicion = pos;
                    index = indice - 1;
                    tipo = TiposClaves.Mascaras;
                }
            }
            return encontrado;
        }

        /// <summary>
        /// Inicializa la lista de datos no encontrados.
        /// </summary>
        public void reiniciarListaDatosNoEncontrados()
        {
            this.datosNoAsignados = new List<string>();
        }
    }
}
